﻿using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Threading;

namespace GroundStation
{
  class Map
  {
    // data structures
    private struct Coord
    {
      public float x;
      public float y;
    };

    private struct Name
    {
      public Coord coord;
      public string text;
    };

    private struct Way
    {
      public byte type;
      public List<Coord> refs;
    };

    List<Way> highway = new List<Way>();
    List<Way> railway = new List<Way>();
    List<Way> forest = new List<Way>();
    List<Way> waterSurface = new List<Way>();
    List<Way> stream = new List<Way>();
    List<Way> residential = new List<Way>();
    List<Name> names = new List<Name>();

    uint maxIdX = 0;  // map y segment count
    uint maxIdY = 0;  // map x segment count
    float maxX = 0;   // map max x
    float maxY = 0;   // map max y
    float minX = 0;   // map min x
    float minY = 0;   // map min y
    uint partX = 0;   // segment width
    uint partY = 0;   // segment height
    uint zoom = 0;    // zoom
    uint actX = 0;    // actual segment id x
    uint actY = 0;    // actual segment id y
    uint actMinX = 0; // actual min x segment id
    uint actMaxY = 0; // actual max y segment id

    float posX = 0;   // actual map position x
    float posY = 0;   // actual map position y
    float lastPosX = 0;
    float lastPosY = 0;
    float offsetPosX = 0;
    float offsetPosY = 0;
    int offsetX = 0;
    int offsetY = 0;

    bool change = false;
    bool zoomed = false;

    //Coord lastTrackCoord = new Coord();
    List<Coord> track = new List<Coord>();

    Coord groundStationPosition = new Coord();
    bool groundStationPositionFixed = false;

    BufferedGraphicsContext context = BufferedGraphicsManager.Current;
    BufferedGraphics buffer = null;
    Bitmap bmp;
    Graphics gBmp;
    Graphics gOut;

    Pen redPen = new Pen(Color.Red, 2);
    Pen blackPen = new Pen(Color.Magenta, 2);
    Pen whitePen = new Pen(Color.Honeydew, 2);
    Pen grayPen = new Pen(Color.DarkSlateGray, 2);
    Pen bluePen = new Pen(Color.Blue, 2);
    Pen brownPen = new Pen(Color.Brown, 2);
    Pen yellowPenBold = new Pen(Color.Yellow, 2);
    Pen redPenBold = new Pen(Color.Red, 3);
    Pen pinkPenBold = new Pen(Color.Tomato, 4);
    Pen violetPenBold = new Pen(Color.HotPink, 5);
    Pen greenPen = new Pen(Color.GreenYellow, 2);

    SolidBrush blackBrush = new SolidBrush(Color.Black);
    SolidBrush blueBrush = new SolidBrush(Color.LightSeaGreen);
    SolidBrush greenBrush = new SolidBrush(Color.LimeGreen);
    SolidBrush grayBrush = new SolidBrush(Color.Gray);
    SolidBrush purpleBrush = new SolidBrush(Color.Purple);

    Font font = new Font("Arial", 8);

    //GpsReceiver gps = new GpsReceiver();

    /// <summary>
    /// Map zoom in
    /// </summary>
    public void ZoomIn()
    {
      if (zoom == 50)
      {
        zoom = 30;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 30)
      {
        zoom = 20;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 20)
      {
        zoom = 15;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 15)
      {
        zoom = 10;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 10)
      {
        zoom = 5;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 5)
      {
        zoom = 2;
        ReadMapConfig(zoom);
        zoomed = true;
      }
    }

    /// <summary>
    /// Map zoom out
    /// </summary>
    public void ZoomOut()
    {
      if (zoom == 30)
      {
        zoom = 50;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 20)
      {
        zoom = 30;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 15)
      {
        zoom = 20;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 10)
      {
        zoom = 15;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 5)
      {
        zoom = 10;
        ReadMapConfig(zoom);
        zoomed = true;
      }
      else if (zoom == 2)
      {
        zoom = 5;
        ReadMapConfig(zoom);
        zoomed = true;
      }
    }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="picturebox">Picturebox.</param>
    public void Init(PictureBox pictureBox)
    {
      buffer = context.Allocate(pictureBox.CreateGraphics(), pictureBox.DisplayRectangle);
      buffer.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
      gOut = buffer.Graphics;

      bmp = new Bitmap(2100, 2100);
      gBmp = Graphics.FromImage(bmp);
      gBmp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

      zoom = 20;
      posX = 1600452.625f;
      posY = 6090545.0f;
      ReadMapConfig(zoom);

      //gps.Init("COM3");
    }

    /// <summary>
    /// Stops gps
    /// </summary>
    public void Stop()
    {
      //gps.Stop();
    }

    /// <summary>
    /// Reads config file of segment
    /// </summary>
    /// <param name="zoom">Zoom.</param>
    private void ReadMapConfig(uint zoom)
    {
      string fileName = "";
      string line = "";
      fileName = "data" + Path.DirectorySeparatorChar + "map" + Path.DirectorySeparatorChar + zoom.ToString() + Path.DirectorySeparatorChar + "config.ini";
      try
      {
        using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
          using (StreamReader sr = new StreamReader(fs))
          {
            while ((line = sr.ReadLine()) != null)
            {
              string[] fields = Regex.Split(line, "=");

              if (fields.Length == 2)
              {
                if (fields[0] == "max_id_x") maxIdX = uint.Parse(fields[1]);
                if (fields[0] == "max_id_y") maxIdY = uint.Parse(fields[1]);
                if (fields[0] == "max_x") maxX = float.Parse(fields[1], System.Globalization.CultureInfo.InvariantCulture);
                if (fields[0] == "max_y") maxY = float.Parse(fields[1], System.Globalization.CultureInfo.InvariantCulture);
                if (fields[0] == "min_x") minX = float.Parse(fields[1], System.Globalization.CultureInfo.InvariantCulture);
                if (fields[0] == "min_y") minY = float.Parse(fields[1], System.Globalization.CultureInfo.InvariantCulture);
                if (fields[0] == "part_x") partX = uint.Parse(fields[1]);
                if (fields[0] == "part_y") partY = uint.Parse(fields[1]);
              }
            }
          }
        }
      }
      catch (Exception)
      { }
    }

    /// <summary>
    /// Loads segment
    /// </summary>
    /// <param name="zoom">Zoom.</param>
    /// <param name="x">X id of segment.</param>
    /// /// <param name="y">Y id of segment.</param>
    private void LoadSegment(uint zoom, uint x, uint y)
    {
      string fileName = "";
      ushort length = 0;

      // objects
      fileName = "data" + Path.DirectorySeparatorChar + "map" + Path.DirectorySeparatorChar + zoom.ToString() + Path.DirectorySeparatorChar + "objects" + Path.DirectorySeparatorChar + x.ToString("0000") + y.ToString("0000") + ".obj";
      if (File.Exists(fileName))
      {
        using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
          using (BinaryReader br = new BinaryReader(fs))
          {
            int pos = 0;
            int brlength = (int)br.BaseStream.Length;
            while (pos < brlength)
            {
              Way newWay = new Way();
              newWay.refs = new List<Coord>();
              try
              {
                newWay.type = br.ReadByte();
                pos += sizeof(byte);
                length = br.ReadUInt16();
                pos += sizeof(UInt16);
                for (uint j = 0; j < length; j++)
                {
                  Coord newCoord = new Coord();
                  newCoord.x = br.ReadSingle();
                  pos += sizeof(float);
                  newCoord.y = br.ReadSingle();
                  pos += sizeof(float);
                  newWay.refs.Add(newCoord);
                }

                switch (newWay.type)
                {
                  case 0:
                  case 1:
                  case 2:
                  case 3:
                  case 4:
                  case 5:
                  case 6:
                  case 7:
                    highway.Add(newWay);
                    break;

                  case 8:
                    railway.Add(newWay);
                    break;

                  case 9:
                    forest.Add(newWay);
                    break;

                  case 10:
                  case 11:
                  case 14:
                    waterSurface.Add(newWay);
                    break;

                  case 12:
                  case 13:
                    stream.Add(newWay);
                    break;

                  case 15:
                    residential.Add(newWay);
                    break;

                  default:
                    break;
                }
              }
              catch (Exception)
              {
              }
            }
          }
        }
      }

      // labels
      fileName = "data" + Path.DirectorySeparatorChar + "map" + Path.DirectorySeparatorChar + zoom.ToString() + Path.DirectorySeparatorChar + "labels" + Path.DirectorySeparatorChar + x.ToString("0000") + y.ToString("0000") + ".lbl";
      if (File.Exists(fileName))
      {
        using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
          using (BinaryReader br = new BinaryReader(fs))
          {
            int pos = 0;
            int brlength = (int)br.BaseStream.Length;
            while (pos < brlength)
            {
              Name newName = new Name();
              newName.coord = new Coord();
              try
              {
                newName.coord.x = br.ReadSingle();
                pos += sizeof(float);
                newName.coord.y = br.ReadSingle();
                pos += sizeof(float);
                length = br.ReadUInt16();
                pos += sizeof(UInt16);
                byte[] buf = new byte[length];
                for (int i = 0; i < length; i++)
                {
                  buf[i] = br.ReadByte();
                  pos += sizeof(byte);
                }
                Encoding enc = System.Text.Encoding.GetEncoding("utf-8");
                newName.text = enc.GetString(buf);
                if (!newName.text.Contains("?"))
                {
                  names.Add(newName);
                }
              }
              catch (Exception)
              {
              }
            }
          }
        }
      }
    }

    /// <summary>
    /// Adds offset on mouse drag
    /// </summary>
    /// <param name="x">Offset X.</param>
    /// /// <param name="y">Offset Y.</param>
    public void AddOffset(long x, long y)
    {
      if (groundStationPositionFixed)
      {
        offsetPosX += x * zoom;
        offsetPosY -= y * zoom;
      }
      else
      {
        posX += x * zoom;
        posY -= y * zoom;
      }
    }

    /// <summary>
    /// Sets gps fix position
    /// </summary>
    public void ShowMe()
    {
      offsetPosX = 0;
      offsetPosY = 0;
    }

    public void AddCoord(float x, float y)
    {
      Coord newCoord = new Coord();

      if (float.IsNaN(x) || float.IsNaN(y))
        return;

      if (lastPosX != x || lastPosY != y)
      {
        newCoord.x = x;
        newCoord.y = y;
        track.Add( newCoord);

        posX = x;
        posY = y;
      }

      // draw
      if (lastPosX != posX || lastPosY != posY || zoomed)
      {
        //gOut.Clear(Color.Black);
        gOut.DrawImage(bmp, offsetX, offsetY, bmp.Width, bmp.Height);
        zoomed = false;
      }

      lastPosX = x;
      lastPosY = y;
    }

    /// <summary>
    /// Updates map
    /// </summary>
    public void Update()
    {
      // got gps fix
      /*if (!groundStationPositionFixed && gps.Valid)
      {
        groundStationPosition.x = gps.X;
        groundStationPosition.y = gps.Y;
        groundStationPositionFixed = true;
        System.Console.WriteLine("map: got gps fix.");
      }

      if (groundStationPositionFixed)
      {
        posX = groundStationPosition.x + offsetPosX;
        posY = groundStationPosition.y + offsetPosY;
      }*/      

      // finds if position has changed
      for (uint i = 0; i <= maxIdX; i++)
      {
        if ((posX - 350 * zoom >= minX + (i * partX)) && (posX - 350 * zoom < minX + ((i + 1) * partX)))
        {
          if (actX == i)
          {
            break;
          }
          actX = i;
          change = true;
          break;
        }
      }

      for (uint j = 0; j <= maxIdY; j++)
      {
        if ((posY - 350 * zoom >= minY + (j * partY)) && (posY - 350 * zoom < minY + ((j + 1) * partY)))
        {
          if (actY == j)
          {
            break;
          }
          actY = j;
          change = true;
          break;
        }
      }

      // redraw map
      if (change)
      {
        System.Console.WriteLine("map: rendering with zoom={0}.", zoom);

        highway.Clear();
        railway.Clear();
        forest.Clear();
        waterSurface.Clear();
        stream.Clear();
        residential.Clear();
        names.Clear();

        // load 4 segments of map with zoom
        LoadSegment(zoom, actX, actY);

        if (actX + 1 <= maxIdX)
        {
          LoadSegment(zoom, actX + 1, actY);
        }

        if (actX + 1 <= maxIdX && actY + 1 <= maxIdY)
        {
          LoadSegment(zoom, actX + 1, actY + 1);
        }

        if (actY + 1 <= maxIdY)
        {
          LoadSegment(zoom, actX, actY + 1);
        }

        // actual bounds
        actMinX = (uint)(minX + (actX * partX));
        actMaxY = (uint)(minY + (actY + 2) * partY);
        gBmp.Clear(Color.LightGray);

        // draw map elements
        for (int i = 0; i < residential.Count; i++)
        {
          Point[] pt = new Point[residential[i].refs.Count];
          for (int j = 0; j < residential[i].refs.Count; j++)
          {
            pt[j].X = (int)((residential[i].refs[j].x - actMinX + 350 * zoom) / zoom); ;
            pt[j].Y = (int)((actMaxY - residential[i].refs[j].y + 350 * zoom) / zoom);
          }
          gBmp.FillPolygon(grayBrush, pt);
        }

        for (int i = 0; i < forest.Count; i++)
        {
          Point[] pt = new Point[forest[i].refs.Count];
          for (int j = 0; j < forest[i].refs.Count; j++)
          {
            pt[j].X = (int)((forest[i].refs[j].x - actMinX + 350 * zoom) / zoom); ;
            pt[j].Y = (int)((actMaxY - forest[i].refs[j].y + 350 * zoom) / zoom);
          }
          gBmp.FillPolygon(greenBrush, pt);
        }

        for (int i = 0; i < stream.Count; i++)
        {
          Point[] pt = new Point[stream[i].refs.Count];
          for (int j = 0; j < stream[i].refs.Count; j++)
          {
            pt[j].X = (int)((stream[i].refs[j].x - actMinX + 350 * zoom) / zoom);
            pt[j].Y = (int)((actMaxY - stream[i].refs[j].y + 350 * zoom) / zoom);
          }
          gBmp.DrawLines(bluePen, pt);
        }

        for (int i = 0; i < waterSurface.Count; i++)
        {
          Point[] pt = new Point[waterSurface[i].refs.Count];
          for (int j = 0; j < waterSurface[i].refs.Count; j++)
          {
            pt[j].X = (int)((waterSurface[i].refs[j].x - actMinX + 350 * zoom) / zoom);
            pt[j].Y = (int)((actMaxY - waterSurface[i].refs[j].y + 350 * zoom) / zoom);
          }
          gBmp.FillPolygon(blueBrush, pt);
        }

        for (int i = 0; i < railway.Count; i++)
        {
          Point[] pt = new Point[railway[i].refs.Count];
          for (int j = 0; j < railway[i].refs.Count; j++)
          {
            pt[j].X = (int)((railway[i].refs[j].x - actMinX + 350 * zoom) / zoom);
            pt[j].Y = (int)((actMaxY - railway[i].refs[j].y + 350 * zoom) / zoom);
          }
          gBmp.DrawLines(grayPen, pt);
        }

        for (int i = 0; i < highway.Count; i++)
        {
          Point[] pt = new Point[highway[i].refs.Count];
          for (int j = 0; j < highway[i].refs.Count; j++)
          {
            pt[j].X = (int)((highway[i].refs[j].x - actMinX + 350 * zoom) / zoom);
            pt[j].Y = (int)((actMaxY - highway[i].refs[j].y + 350 * zoom) / zoom);
          }
          if (highway[i].type == 0) gBmp.DrawLines(violetPenBold, pt); // dalnice
          if (highway[i].type == 1 || highway[i].type == 2) gBmp.DrawLines(pinkPenBold, pt); // I.trida
          if (highway[i].type == 3) gBmp.DrawLines(redPenBold, pt); // II.trida
          if (highway[i].type == 4) gBmp.DrawLines(yellowPenBold, pt); // III.trida
          if (highway[i].type == 5) gBmp.DrawLines(whitePen, pt); // ulice
          if (highway[i].type == 6 || highway[i].type == 7) gBmp.DrawLines(brownPen, pt);  // polni cesta
        }

        // draw labels
        for (int i = 0; i < names.Count; i++)
        {
          Point pt = new Point();
          pt.X = (int)((names[i].coord.x - actMinX + 350 * zoom) / zoom);
          pt.Y = (int)((actMaxY - names[i].coord.y + 350 * zoom) / zoom);

          gBmp.DrawString(names[i].text, font, blackBrush, pt);
        }

        // draw position
        Rectangle rect = new Rectangle();
        rect.X = (int)((groundStationPosition.x - actMinX + 350 * zoom) / zoom);
        rect.Y = (int)((actMaxY - groundStationPosition.y + 350 * zoom) / zoom);
        rect.Width = 10;
        rect.Height = 10;
        gBmp.FillEllipse(purpleBrush, rect);

        change = false;
      }
      offsetX = -(int)((posX - actMinX) / zoom);
      offsetY = -(int)((actMaxY - posY) / zoom);

      if (buffer != null)
      {
        if (track.Count > 1)
        {
          Point[] pts = new Point[track.Count];
          for (int j = 0; j < track.Count; j++)
          {
            pts[j].X = (int)((track[j].x - actMinX + 350 * zoom) / zoom);
            pts[j].Y = (int)((actMaxY - track[j].y + 350 * zoom) / zoom);
          }
          gBmp.DrawLines(blackPen, pts);
        }

        buffer.Render();

        gOut.DrawLine(blackPen, new Point(340, 350), new Point(360, 350));
        gOut.DrawLine(blackPen, new Point(350, 340), new Point(350, 360));
      }

      lastPosX = posX;
      lastPosY = posY;
    }
  }
}